package simpleftp.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.Callable;
import org.json.simple.*;
import simpleftp.client.control.Command;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * * * @author lom & gramage & masson
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files.
 */
public class FTPServer implements Callable<Void> {

    private static boolean DEBUG = false;
    private String response, user;
    private int validations;
    private File f;
    private StringBuilder sb;
    private int portnum1, portnum2;
    private Properties p;
    private ServerSocket dl;
    private Socket dl2;
    private FileInputStream fis;
    private Socket socket = null;
    private BufferedReader reader = null;
    private BufferedWriter writer = null;
    // TO BE COMPLETED - 1 LINE - OK
    private ObjectOutputStream os = null;

    //JSon objects
    private String sJson;
    private JSONObject oJson;

    public FTPServer(Socket s) {
        socket = s;
        validations = 0;
        try {
            p = new Properties();
            fis = new FileInputStream("users.properties");
            p.load(fis);
            try {
                reader = new BufferedReader(new InputStreamReader(
                        socket.getInputStream()));
                writer = new BufferedWriter(new OutputStreamWriter(
                        socket.getOutputStream()));
                // TO BE COMPLETED - 1 LINE - OK
                os = new ObjectOutputStream(s.getOutputStream());

                sendLine("220 ");
                validations++;
                f = new File(System.getProperty("user.dir"));

                System.out.println("FTP Server : new client from " + socket.getInetAddress().getHostAddress());

                //JSON Object creation
                sJson = createJSON(f, 1);
                sJson = "{\n" + "\"" + f.getName() + "\":\n\t[\n" + sJson + "\t], \"folder\":true, \"name\":\"" + f.getName() + "\"\n}";

                JSONParser JSONparser = new JSONParser();
                Object object = null;
                try {
                    object = JSONparser.parse(sJson);
                } catch (ParseException ex) {
                    System.out.println("Parse exception.");
                }
                oJson = (JSONObject) object;

            } catch (IOException e) {
                try {
                    sendLine("error");
                } catch (IOException e1) {
                    System.out.println("IO exception");
                }
                System.out.println("IO exception");
            }
        } catch (IOException ioex) {
            System.out.println("Error: Properties File didn't load");
            try {
                sendLine("error ");
            } catch (IOException e) {
            }
        }
    }

    @Override
    public Void call() {
        try {
            response = readLine();
            if (response.startsWith("USER")) {
                if (p.containsKey(response.substring(5))) {
                    user = response.substring(5);
                    System.out.println("Server knows user " + response);
                    sendLine("331 ");
                    validations++;
                } else {
                    System.out.println("Server does no know user " + response);
                    sendLine("error ");
                }
            }
            response = readLine();
            if (response.startsWith("PASS")) {
                if (p.get((String) user).equals(response.substring(5))) {
                    System.out.println("Server validates pass " + response);
                    sendLine("230 ");
                    validations++;
                } else {
                    sendLine("error ");
                }
            }

        } catch (IOException e) {
            // TODO Auto-generated catch block
            try {
                sendLine("error ");
            } catch (IOException e1) {
            }
        }

        while (validations == 3) {
            try {
                response = readLine();

                switch (Command.valueOf(response.split(" ")[0])) {

                    case PWD:
                        sendLine("257 " + System.getProperty("user.dir"));
                        break;

                    case LIST:
                        sb = new StringBuilder("200 ");
                        String[] fileNames = f.list();
                        for (String fileName : fileNames) {
                            sb = sb.append(fileName).append("|");
                        }

                        sendLine(sb.toString());
                        // TO BE COMPLETED - 1 LINE
                        //sendSer(f); //Serialisation
                        sendSerJSON(oJson); //Json
                        break;

                    case CWD:
                        System.out.println("CWD - " + response.substring(4));
                        System.setProperty("user.dir", response.substring(4));
                        System.out.println("CWD new current directory:"
                                + System.getProperty("user.dir").toString());
                        f = new File(System.getProperty("user.dir"));
                        sendLine("250 ");
                        break;

                    case PASV:
                        pasv();
                        break;

                    case STOR:
                        stor();
                        break;

                    case RETR:
                        stordown();
                        break;

                    case QUIT:
                        System.out.println("QUIT :Goodbye");
                        socket.close();
                        validations = 0;
                        break;

                    default:
                        System.out.println("Unknown Command.");
                        sendLine("error");
                        break;

                }
            } catch (IOException e) {
                try {
                    sendLine("error");
                } catch (IOException e1) {
                }
            }

        }
        Void Void = null;
        return Void;
    }

    // TO BE COMPLETED - SEND SERIALIZABLE OBJECT OVER THE NETWORK - 5 LINES  - OK
    private void sendSer(File file) {
        try {
            os.writeObject(file);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    private void sendSerJSON(JSONObject oJson) {
        try {
            os.writeObject(oJson);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

    private String createJSON(File home, int lvl) {
        String stringJson = "";

        File[] l = home.listFiles();
        int j = 0;

        if (l == null) {
            System.out.println("NULL");
            return "";
        } else {
            for (File name : l) {
                if (name.isDirectory()) {
                    //if file is a directory, 
                    for (int i = 0; i < lvl; i++) {
                        stringJson += "\t";
                    }
                    stringJson += "{\"" + name.getName() + "\":" + "\n";

                    for (int i = 0; i < lvl; i++) {
                        stringJson += "\t";
                    }
                    stringJson += "[\n";
                    stringJson += this.createJSON(name, (++lvl));

                    for (int i = 0; i < lvl - 1; i++) {
                        stringJson += "\t";
                    }
                    stringJson += "], \"folder\":true, \"name\":\"" + name.getName() + "\"}";
                    if (j != (l.length - 1)) {
                        stringJson += ",";
                    }
                    stringJson += "\n";
                    lvl = lvl - 1;
                } else {
                    //for normal file
                    for (int i = 0; i < lvl; i++) {
                        stringJson += "\t";
                    }
                    stringJson += "{\"name\":";
                    stringJson += "\"" + name.getName() + "\", \"folder\":false}";
                    if (j != (l.length - 1)) {
                        stringJson += ",";
                    }
                    stringJson += "\n";
                }
                j++;
            }
        }
        return (stringJson);
    }

    /**
     * Sends a raw command to the FTP server.
     */
    private void sendLine(String line) throws IOException {
        if (socket == null) {
            throw new IOException("FTPServer is not connected.");
        }
        try {
            writer.write(line + "\r\n");
            writer.flush();
            if (DEBUG) {
                System.out.println("> " + line);
            }
        } catch (IOException e) {
            socket = null;
            throw e;
        }
    }

    private String readLine() throws IOException {
        String line = reader.readLine();
        if (DEBUG) {
            System.out.println("< " + line);
        }
        return line;
    }

    private void stor() {
        try {
            dl2 = dl.accept();
            sendLine("150 ");
            InputStream is;
            try (OutputStream osf = new FileOutputStream(new File(
                    System.getProperty("user.dir")
                            + System.getProperty("file.separator")
                            + response.substring(5)))) {
                is = dl2.getInputStream();
                byte[] buffer = new byte[4096];
                int bytesRead = 0;
                while ((bytesRead = is.read(buffer)) != -1) {
                    osf.write(buffer, 0, bytesRead);
                }   osf.flush();
            }
            is.close();
            sendLine("226 ");
            dl.close();
            dl2.close();
            System.out.println("STOR " + response.split(" ")[1]
                    + " : File received");
        } catch (IOException e) {
            try {
                sendLine("error");
            } catch (IOException e1) {
            }
        }
    }

    private void stordown() {
        try {
            dl2 = dl.accept();
            sendLine("150 ");
            InputStream is;
            try (OutputStream osf = new FileOutputStream(new File(response.substring(5)))) {
                is = dl2.getInputStream();
                byte[] buffer = new byte[4096];
                int bytesRead = 0;
                while ((bytesRead = is.read(buffer)) != -1) {
                    osf.write(buffer, 0, bytesRead);
                }   osf.flush();
            }
            is.close();
            sendLine("227 ");
            dl.close();
            dl2.close();
            System.out.println("RETR " + response.split(" ")[1]
                    + " : File transfered");
        } catch (IOException e) {
            try {
                sendLine("error");
            } catch (IOException e1) {
                System.out.println(e1.getMessage());
            }
            System.out.println(e.getMessage());
        }
    }

    private void pasv() {
        try {
            dl = new ServerSocket(0);
            portnum1 = dl.getLocalPort() / 256;
            portnum2 = dl.getLocalPort() % 256;
            sendLine("227 ("
                    + InetAddress.getLocalHost().getHostAddress()
                    .replace('.', ',') + "," + portnum1 + ","
                    + portnum2 + ")");
            System.out.println("PASV success");
        } catch (IOException e) {
            try {
                sendLine("error ");
                System.out.println("pasv error");
            } catch (IOException e1) {
            }
        }
    }

    public static void setDebug(boolean b) {
        DEBUG = b;
    }
}
